﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace DomainDriver.CommonUtilities.Reflectors
{
    public delegate DATATYPE PropertyGetterDelegate<DATATYPE>();

    public static class ClassReflector
    {
        public static IList<FieldInfo> GetValueTypeAndStringFields<T>(T obj)
        {
            Type typeOfObject = typeof(object);
            Type typeToCopy = typeof(T);
            BindingFlags bindingFlagsToUse = BindingFlags.NonPublic | BindingFlags.Instance;
            MemberInfo[] objectMembers = typeOfObject.GetMembers(bindingFlagsToUse);
            MemberInfo[] members = typeToCopy.GetMembers(bindingFlagsToUse);

            List<FieldInfo> fieldsToCopy = new List<FieldInfo>();
            foreach (MemberInfo member in members)
            {
                if (member.MemberType != MemberTypes.Field)
                { continue; }
                if (objectMembers.Where<MemberInfo>((MemberInfo objectMember) => objectMember.Name.Equals(member.Name)).Count<MemberInfo>() > 0)
                { continue; }

                string testString = "A";
                FieldInfo field = typeToCopy.GetField(member.Name, bindingFlagsToUse);
                if (!field.FieldType.IsValueType && !field.FieldType.IsInstanceOfType(testString))
                { continue; }

                fieldsToCopy.Add(field);
            }

            return fieldsToCopy;
        }

        public static void CopyValueTypesAndStrings<T>(T objectToCopyFrom, T objectToCopyTo)
        {
            IList<FieldInfo> fieldsToCopy = GetValueTypeAndStringFields<T>(objectToCopyFrom);

            foreach (FieldInfo field in fieldsToCopy)
            {
                object valueToCopy = field.GetValue(objectToCopyFrom);
                field.SetValue(objectToCopyTo, valueToCopy);
            }
        }

        public static bool CheckEqualityForValueTypesAndStrings<T>(T object1, T object2)
        {
            IList<FieldInfo> fieldsToCopy = GetValueTypeAndStringFields<T>(object1);

            foreach (FieldInfo field in fieldsToCopy)
            {
                object object1Value = field.GetValue(object1);
                object object2Value = field.GetValue(object2);

                if (object1Value == null)
                {
                    if (object2Value != null)
                    { return false; }
                }
                else
                {
                    if (!object1Value.Equals(object2Value))
                    { return false; }
                }
            }
            return true;
        }

        public static IList<PropertyInfo> GetPropertiesWithAttributeDeclaration<ATTRIBUTE>(Type classType)
            where ATTRIBUTE : Attribute
        {
            IDictionary<Type, IList<PropertyInfo>> propertiesByAttributeType = GetPropertiesForAttributeType(classType);
            List<PropertyInfo> matchingProperties = new List<PropertyInfo>();

            foreach (Type attributeType in propertiesByAttributeType.Keys)
            {
                if (attributeType.Equals(typeof(ATTRIBUTE)))
                {
                    matchingProperties = new List<PropertyInfo>(propertiesByAttributeType[attributeType]);
                }
            }

            return matchingProperties;
        }

        public static IDictionary<Type, IList<PropertyInfo>> GetPropertiesForAttributeType(Type classType)
        {
            Dictionary<Type, IList<PropertyInfo>> propertiesByAttributeType = new Dictionary<Type, IList<PropertyInfo>>();

            foreach (PropertyInfo propertyInfo in classType.GetProperties())
            {
                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
                {
                    Type attributeType = attribute.GetType();

                    if (!propertiesByAttributeType.ContainsKey(attributeType))
                    {
                        propertiesByAttributeType.Add(attributeType, new List<PropertyInfo>());
                    }
                    propertiesByAttributeType[attributeType].Add(propertyInfo);
                }
            }

            return propertiesByAttributeType;
        }

        public static PropertyInfo GetPropertyByName(Type classType, string propertyName)
        {
            foreach (PropertyInfo propertyInfo in classType.GetProperties())
            {
                if (propertyInfo.Name == propertyName)
                {
                    return propertyInfo;
                }
            }
            return null;
        }

        public static List<PropertyInfo> GetNestedPropertiesByName(Type classType, string propertyName)
        {
            List<PropertyInfo> returnValue = new List<PropertyInfo>();
            char separator = '.';
            string processedName = propertyName;
            string remainingName = string.Empty;
            bool containsPeriod = propertyName.Contains(separator);

            if (containsPeriod)
            {
                string[] splitStrings = propertyName.Split(new char[] { separator }, 2);
                processedName = splitStrings[0];
                remainingName = splitStrings[1];
            }

            PropertyInfo propertyInfo = classType.GetProperty(processedName);
            if (propertyInfo == null)
            { return returnValue; }

            returnValue.Add(propertyInfo);
            List<PropertyInfo> recursionValue = GetNestedPropertiesByName(propertyInfo.PropertyType, remainingName);
            foreach (PropertyInfo info in recursionValue)
            {
                returnValue.Add(info);
            }
            return returnValue;
        }

        public static object GetNestedPropertyValue(object instance, IList<PropertyInfo> properties)
        {
            object nestedObject = instance;
            foreach (PropertyInfo propertyInfo in properties)
            {
                nestedObject = propertyInfo.GetValue(nestedObject, null);
            }
            return nestedObject;
        }

        public static object SetNestedPropertyValue(object instance, IList<PropertyInfo> properties, object value)
        {
            object nestedObject = instance;
            PropertyInfo lastPropertyInfo = properties.Last<PropertyInfo>();
            foreach (PropertyInfo propertyInfo in properties)
            {
                if (propertyInfo == lastPropertyInfo)
                {
                    propertyInfo.SetValue(nestedObject, value, null);
                }
                else
                {
                    nestedObject = propertyInfo.GetValue(nestedObject, null);
                }
            }
            return nestedObject;
        }

        public static string GetNameForProperty<PROPERTY_DATATYPE>(PropertyGetterDelegate<PROPERTY_DATATYPE> propertyGetter)
        {
            return propertyGetter.Method.Name;
        }

        /*public static string GetStaticPropertyName<T>(Expression<Func<T>> expression)
        {
            MemberExpression body = (MemberExpression)expression.Body;
            return body.Member.Name;
        }*/

        public static string GetPropertyNameForDelegateType<DELEGATE_TYPE>(Expression<DELEGATE_TYPE> expression)
        {
            return GetPropertyNameForExpression(expression.Body);
        }

        public static string GetPropertyNameForExpression(Expression expression)
        {
            UnaryExpression unaryExpression = expression as UnaryExpression;
            if (unaryExpression != null)
            {
                return GetPropertyNameForExpression(unaryExpression.Operand);
            }

            MemberExpression memberExpression = expression as MemberExpression;
            if (memberExpression != null)
            {
                string innerPropertyName = GetPropertyNameForExpression(memberExpression.Expression);
                string outerPropertyName = memberExpression.Member.Name;

                if (string.IsNullOrEmpty(innerPropertyName))
                { return outerPropertyName; }
                return innerPropertyName + "." + outerPropertyName;
            }
            return string.Empty;
        }

        public static string GetPropertyName<T, TReturn>(Expression<Func<T, TReturn>> expression)
        {
            return GetPropertyNameForDelegateType<Func<T, TReturn>>(expression);
        }

        public static ICollection<string> ConvertExpressionsToNames<T, TReturn>(ICollection<Expression<Func<T, TReturn>>> propertyExpressions)
        {
            List<string> propertyNames = new List<string>();
            foreach (Expression<Func<T, TReturn>> propertyExpression in propertyExpressions)
            {
                propertyNames.Add(ClassReflector.GetPropertyName<T, TReturn>(propertyExpression));
            }
            return propertyNames;
        }

        public static string GetPropertyName<TReturn>(Expression<Func<TReturn>> expression)
        {
            return GetPropertyNameForDelegateType<Func<TReturn>>(expression);
        }
    }
}